#include "OrbitCamera.hpp"

using namespace Ogre;

// Constructor
OrbitCamera::OrbitCamera()
{
	m_pFollowNode	= 0;
	m_pPositionNode	= 0;

	m_pCamera		= 0;
	m_pSceneMgr		= 0;

	m_bIsCameraRotationAllowed	= false;
	
	// Constants used for movement.
	m_fDistanceToPositionNode	= 75.0f;
	m_fRotationMultiplier		= 0.35f;
	m_fZoomMultiplier			= 0.35f;
	m_fDragAccel				= 500.0f;
	m_fRotationTopSpeed			= 0.10f;

	// Variables used for smooth rotations.
	m_fPitchVelocity			= 0.0f;
	m_fPitchOldVelocity			= 0.0f;
	m_fPitchAccel				= 0.0f;
	m_fYawVelocity				= 0.0f;
	m_fYawAccel					= 0.0f;
	m_fYawOldVelocity			= 0.0f;
	m_fZoomVelocity				= 0.0f;
	m_fZoomOldVelocity			= 0.0f;
	m_fZoomAccel				= 0.0f;
}

// Destructor
OrbitCamera::~OrbitCamera()
{
	// All the scene node and OIS deletes are handled in a higher class.
}

// The create function initializes all the values that are used for the Camera.
void OrbitCamera::createOrbitCamera(Ogre::SceneManager* i_pSceneMgr, Ogre::SceneNode* i_pFollowNode, Ogre::Camera* i_pCamera, OIS::Mouse* i_pMouse)
{
	// Set the default properties of the camera and move the Camera
	setFollowNode(i_pFollowNode);
	setCamera(i_pCamera);

	// Set the Mouse pointer to the one we were given.
	m_pMouse = i_pMouse;

	// Create the node that the the camera uses for position and LookAt Functionality.
	setSceneManager(i_pSceneMgr);
	m_pPositionNode = i_pSceneMgr->getRootSceneNode()->createChildSceneNode("OrbitCameraPositionNode");
	m_pPositionNode->attachObject(m_pCamera);
	m_pPositionNode->setPosition( m_pFollowNode->getPosition() );
	m_pCamera->setPosition(0.0f, 0.0f, m_fDistanceToPositionNode);
	m_pCamera->lookAt(0.0f, 0.0f, 0.0f);

	// Set the camera to allow yaw rotations. Gets you a Nexus style camera.
	m_pCamera->setFixedYawAxis(false);

	// Set the camera to the location of the node we are trying to follow.
	updateOrbitCamera(0.0);
}

// This function updates the position of the node that the camera uses for positition
// tracking. If there is no node for us to follow, then just sit still. If there is 
// a something for the camera to be following, then set the position node to have the
// same position as the follow node. This should be called in the update cycle of
// whatever ends up using this. 
void OrbitCamera::updateOrbitCamera(double timeSinceLastFrame)
{
	// Set the mouse related camera movement parameters.
	setCameraOrientationAndDistance(timeSinceLastFrame);

	// If nothing to follow, then sit still. In the future, I want to make the node
	// come to a smooth stop rather than just halt.
	if (m_pFollowNode == 0)
		return;

	// If there is something to follow, then move our position to match it.
	else
		m_pPositionNode->setPosition( m_pFollowNode->getPosition() );
}

// TODO: COMPLETELY REWRITE THIS SO IT'S NOT FRAME DEPENDANT. 
// Separate function for setting mouse related camera movement.
void OrbitCamera::setCameraOrientationAndDistance(double timeSinceLastFrame)
{
	// First, in a really hacky hardcoded way, adjust for the FPS. This works at FPS>60
	float fOldMultiplier, fNewMultiplier;
	fNewMultiplier = (float)timeSinceLastFrame/0.23809523809528571428571428571429f;

	// Get the other multiplier.
	fOldMultiplier = 1-fNewMultiplier;

	// Use the unbuffered input to calculate the rotations for the orbiting camera.
	// The camera can also only rotate when the right mouse button is held down.
	if (m_bIsCameraRotationAllowed)
	{
		m_fYawVelocity = m_pMouse->getMouseState().X.rel * -m_fRotationMultiplier;
		m_fYawVelocity = (m_fYawOldVelocity*fOldMultiplier + m_fYawVelocity*fNewMultiplier);
		m_fYawOldVelocity = m_fYawVelocity;

		m_fPitchVelocity = m_pMouse->getMouseState().Y.rel * -m_fRotationMultiplier;
		m_fPitchVelocity = (m_fPitchOldVelocity*fOldMultiplier + m_fPitchVelocity*fNewMultiplier);
		m_fPitchOldVelocity = m_fPitchVelocity;

		m_pPositionNode->yaw(Degree(m_fYawVelocity));
		m_pPositionNode->pitch(Degree(m_fPitchVelocity));
	}
	else
	{
		m_fYawVelocity = 0.0f;
		m_fYawVelocity = (m_fYawOldVelocity*fOldMultiplier + m_fYawVelocity*fNewMultiplier);
		m_fYawOldVelocity = m_fYawVelocity;

		m_fPitchVelocity = 0.0f;
		m_fPitchVelocity = (m_fPitchOldVelocity*fOldMultiplier + m_fPitchVelocity*fNewMultiplier);
		m_fPitchOldVelocity = m_fPitchVelocity;

		m_pPositionNode->yaw(Degree(m_fYawVelocity));
		m_pPositionNode->pitch(Degree(m_fPitchVelocity));
	}

	// Calculate the new desired zoom distance.
	m_fZoomVelocity = m_pMouse->getMouseState().Z.rel * -m_fZoomMultiplier;
	m_fZoomVelocity = (m_fZoomOldVelocity*fOldMultiplier + m_fZoomVelocity*fNewMultiplier);
	m_fZoomOldVelocity = m_fZoomVelocity;
	m_fDistanceToPositionNode = m_fZoomVelocity + m_fDistanceToPositionNode;

	// Don't let teh camera get past the ship.
	if (m_fDistanceToPositionNode < 0.0f)
		m_fDistanceToPositionNode = 0.0f;

	// Move the camera to the new zoom distance as best as we can.
	m_pCamera->setPosition(0.0f, 0.0f, m_fDistanceToPositionNode);
}

// Sets the pointer to the SceneManager we are operating in.
void OrbitCamera::setSceneManager(Ogre::SceneManager* i_pSceneMgr)
{
	m_pSceneMgr = i_pSceneMgr;
}

// Sets which camera this class should be modifying.
void OrbitCamera::setCamera(Ogre::Camera* i_pCamera)
{
	m_pCamera = i_pCamera;
}

// Sets the pointer to the SceneNode that the Camera follows.
void OrbitCamera::setFollowNode(Ogre::SceneNode* i_pFollowNode)
{
	m_pFollowNode = i_pFollowNode;
}

// When the right mouse button is pressed down, camera rotation is allowed.
void OrbitCamera::injectMouseButtonDown(const OIS::MouseButtonID& id)
{
	if (id == OIS::MB_Right)
		m_bIsCameraRotationAllowed = true;
}

// When the right mouse button is released, camera rotation is no longer allowed.
void OrbitCamera::injectMouseButtonRelease(const OIS::MouseButtonID& id)
{
	if (id == OIS::MB_Right)
		m_bIsCameraRotationAllowed = false;
}